#!/usr/bin/env node

var fs = require('fs-extra');
var path = require('path');
var pbjs = require('protobufjs/cli/pbjs')
var globArray = require('glob-array');
var proto2ts = require('./lib/proto2typescript');
var proto2go = require('./lib/proto2go');
var DustJS = require("dustjs-linkedin");
var idmapParser = require('./lib/idmap/index');
var idmapParser2 = require('./lib/idmap/index');

var _ = require('lodash');
var argv = require('yargs')
        .help('h')
        //.alias('f', 'file').describe('f', '指定协议文件')
        //.alias('g', 'gen').describe('g', '生成对应语言的文件')
        //.alias('o', 'out').describe('o', '输出目录')
        .option('file', {alias: 'f', describe: '指定协议文件', demand: true})
        .option('mergefile', {alias: 'm', describe: '合并协议文件(可选)', demand: false})
        .option('gen', {alias: 'g', describe: '生成对应语言的文件', choices: ['ts','tscommon', 'goclient', 'goserver', 'goplat', 'json', 'jsonschema'], demand: true})
        .option('out', {alias: 'o', describe: '输出目录', default: './gen'})
        .argv;
function checkDuplicateIdMap(info){
    //检测 idMap 是否有重复的 cmdId
    var mymap =new Map();
    for (var id in info.idMap){
        if(mymap.get(id)){
            throw "idmap duplicate with :" + id + info.idMap[id]
        }
        mymap.set(id,info.idMap[id])
    }

    //检测 enums 是否有重复的枚举
    var mymap =new Map();
    for (var id in info.enums){
        if(mymap.get(id)){
            throw  false,"enums duplicate with :" + id + info.enums[id]
        }
        mymap.set(id,info.enums[id])
    }
}
function genproto(fileName,mergefile) {
    if (mergefile){
        var infoMerge=idmapParser2(mergefile);
    }
    var info = idmapParser(fileName);
    checkDuplicateIdMap(info)
    if (argv.gen == 'goserver') {
        outputGoServer({packageName: info.packageName, idMap: info.idMap, reqMap: info.reqMap, enums: info.enums});
        return;
    }
    if (argv.gen == 'goclient') {
        outputGoClient({packageName: info.packageName, idMap: info.idMap, reqMap: info.reqMap, enums: info.enums});
        return;
    }
    if (argv.gen == 'goplat') {
        outputGoPlat({packageName: info.packageName, idMap: info.idMap, reqMap: info.reqMap, enums: info.enums});
        return;
    }
    // console.log("info.pbFiles="); console.dir(info.pbFiles);
    // console.log("info.packageName="); console.dir(info.packageName);
    // console.log("info.idMap="); console.dir(info.idMap);
    // console.log("info.reqMap="); console.dir(info.reqMap);
    // console.log("info.enums="); console.dir(info.enums);
    var pbFiles = info.pbFiles;
    if (typeof pbFiles === 'string') pbFiles = [pbFiles];
    pbFiles = pbFiles.map(file => path.join(path.dirname(fileName)!=undefined?path.dirname(fileName):path.dirname(mergefile), file))
    var files = globArray.sync(pbFiles, null);
    var includePath = [];
    files.forEach(file => {
        var p = path.resolve(path.dirname(file));
        if (includePath.indexOf(p) < 0)
            includePath.push(p);
    });
    //额外的 pbFiles
    if (infoMerge){
        var pbfile2=infoMerge.pbFiles;
        if (typeof pbfile2 === 'string') pbfile2 = [pbfile2];
        pbfile2 = pbfile2.map(file => path.join(path.dirname(mergefile), file))
        var files2 = globArray.sync(pbfile2, null);
        files2.forEach(file => {
            files.push(file)

            var p = path.resolve(path.dirname(file));
            if (includePath.indexOf(p) < 0)
                includePath.push(p);
            //额外包含上层目录,避免包含文件时 pbcommon/common.proto 的情况    
            var p = path.resolve(path.dirname(path.dirname(file)));
            if (includePath.indexOf(p) < 0)
                includePath.push(p);
        });
        // //额外的 enums 数组
        // for (var id in infoMerge.enums){
        //     info.enums.push(infoMerge.enums[id])
        // }
        // //额外的 idMap reqMap
        // for (var id in infoMerge.idMap){
        //     info.idMap[id]=infoMerge.idMap[id]
        // }
        // for (var id in infoMerge.reqMap){
        //     info.reqMap[id]=infoMerge.reqMap[id]
        // }
    }
    var options = {
        path: includePath,
    };

    var builder = pbjs.sources['proto'](files, options)
    var result = pbjs.targets['json'](builder, options);
    result = JSON.parse(result);
    result.idMap = info.idMap;
    //没有找到在哪里可以设置 ，这里强制设置成proto3
    result.syntax="proto3"

    if (argv.gen=='jsonschema'){
        var rpcs = _.flatten(_.map(result.services, service => _.values(service.rpc)));
        var reqs = _.map(rpcs, rpc => rpc.request);
        reqs = _.merge(reqs, _.values(info.reqMap));
        outputJsonSchema(files, reqs);
    	//outputJsonSchema(files,info.reqMap);
    	return;
    }


    if (argv.gen == 'json') {
        result.reqMap = info.reqMap
        fs.outputFileSync(path.join(argv.out, 'proto.json'), JSON.stringify(result, null, 4));
    } else if (argv.gen == 'ts'){
        result.consts = info.enums
        outputTs(JSON.stringify(result))
        //outputIdMapTs(JSON.stringify(result.idMap))
    } else if (argv.gen == 'tscommon'){
        result.consts = info.enums
        outputTsCommon(JSON.stringify(result))
    }
}

function outputIdMapTs(source) {
    //module pb{
    //  export  var IdMap={1:pb.LoginAck,2:pb.LoginGateAck}
    //  export  var NameMap={"LoginGateReq":201}
    // }
    //console.log(source)
    var jsonmap=JSON.parse(source)
   // console.log(jsonmap)
    var out="module pb{ \n";
    var t1="  export  var IdMap={"
    var t2="  export  var NameMap={"
    for(var p in jsonmap){
        t1+=(p+":pb."+jsonmap[p]+",")
        t2+=("\""+jsonmap[p]+"\":"+p+",")
    }
    t1=t1.substring(0,t1.length-1)+"}\n"
    t2=t2.substring(0,t2.length-1)+"}\n"
    out+=t1
    out+=t2
    out+="}"
    fs.outputFileSync(path.join(argv.out, 'IdMap.ts'), out);
}

function outputTs(source) {
    proto2ts.genDeclaration(source, {properties: true}, (err, out) => {
        if (err) return console.log(err);
        fs.outputFileSync(path.join(argv.out, 'proto.d.ts'), out);
    });
    proto2ts.genDefinition(source, (err, out) => {
        if (err) return console.log(err);
        fs.outputFileSync(path.join(argv.out, 'proto.js'), out);
    });
}
function outputTsCommon(source) {
    proto2ts.genDeclaration(source, {properties: true}, (err, out) => {
        if (err) return console.log(err);
        fs.outputFileSync(path.join(argv.out, 'protoBase.d.ts'), out);
    });
    proto2ts.genDefinition(source, (err, out) => {
        if (err) return console.log(err);
        fs.outputFileSync(path.join(argv.out, 'protoBase.js'), out);
    });
}

function outputGoServer(source) {
    proto2go.genIdMapServer(source, (err, out) => {
        if (err) return console.log(err);
        fs.outputFileSync(path.join(argv.out, 'idmap.go'), out);
    });
    proto2go.genEnums(source, (err, out) => {
        if (err) return console.log(err);
        fs.outputFileSync(path.join(argv.out, 'const.go'), out);
    });
}
function outputGoClient(source) {
    proto2go.genIdMapClient(source, (err, out) => {
        if (err) return console.log(err);
        fs.outputFileSync(path.join(argv.out, 'idmap.go'), out);
    });
    proto2go.genEnums(source, (err, out) => {
        if (err) return console.log(err);
        fs.outputFileSync(path.join(argv.out, 'const.go'), out);
    });
}

function outputGoPlat(source) {
    proto2go.genIdMapGoPlat(source, (err, out) => {
        if (err) return console.log(err);
        fs.outputFileSync(path.join(argv.out, 'idmap.go'), out);
    });
    proto2go.genEnums(source, (err, out) => {
        if (err) return console.log(err);
        fs.outputFileSync(path.join(argv.out, 'const.go'), out);
    });
}

function outputJsonSchema(pbFiles,reqMap){
    var protobuf2jsonschema = require("protobuf-jsonschema")

    var allObj = {};
    // 由于protobuf-jsonschema不能正确处理map<int32, int32>类型，故这里每个Message独自解析
    pbFiles.forEach(file => {
        reqMap.forEach(pbMessage => {
            try {
                var obj = protobuf2jsonschema(file, 'pb.' + pbMessage);
                allObj[pbMessage] = obj;
            } catch(err) {
            }
        })
    });
    
    
    var f, i, j, len, len1, req;    
    //
    function loadOne(file,pbMessage){
        try{
            var one = protobuf2jsonschema(file, "pb."+pbMessage);
        }catch(err){
        }
        if (one) {
            //console.log("pbMessage ",pbMessage)
            //console.dir(one)
            allObj[pbMessage] = one;
        }
    }

    //遍历所有的proto文件
    for (i = 0, len = pbFiles.length; i < len; i++) {
        f = pbFiles[i];
        //console.log("pbFiles[",i,"]=",f)
        for (k in reqMap) {
            req = reqMap[k];
            loadOne(f,req)
        }
    }
    // console.log("All pbMessage ")
    // console.dir(allObj)

    var writeFilePath = path.join(argv.out,'protoschema.json')
    fs.outputFileSync(writeFilePath, JSON.stringify(allObj, null, 4));
    console.log("generate protoschema.json done. -> "+writeFilePath)
    return allObj;
}

module.exports = genproto

if (require.main == module) {
    if (!argv.file) {
        console.log("please specify a file");
        return;
    }
    var file = path.isAbsolute(argv.file) ? argv.file : path.join(process.cwd(), argv.file);
    var mergefile = undefined
    if (argv.mergefile){
        mergefile = path.isAbsolute(argv.mergefile) ? argv.mergefile : path.join(process.cwd(), argv.mergefile);
    }

    if (!fs.existsSync(file)) {
        throw 'file not exist :' + file;
        return;
    }
    if (mergefile){
        console.log("mergefile is " + mergefile)
    }
    if (mergefile && !fs.existsSync(mergefile)) {
        throw "mergefile not exist: " + mergefile;
        return;
    }
    if (!argv.out)
        argv.out = './gen';
    if (!fs.existsSync(argv.out))
        fs.ensureDir(argv.out);
    genproto(file,mergefile);
    console.log("genproto of [" + argv.file + "], [" + argv.gen + "]  done")
}

